#include "ImagePreviewCtrl.h"
// #include "../3rd/include/freeimage/FreeImage.h"
#include <utility>
#include <gdiplus.h>
#pragma comment (lib, "gdiplus")


CImagePreviewCtrl::CImagePreviewCtrl(CxDocument *pDoc)
	: gdiplusToken(NULL)
	, m_pDoc(pDoc)
	, m_bitmap(NULL)
	, m_isLoadingFileRemoved(false)
	, m_semaphore(NULL)
	, m_exiting(false)
{
	::InitializeCriticalSection(&m_cs);
	ATLASSERT (m_pDoc);
}

CImagePreviewCtrl::~CImagePreviewCtrl(void) {
}

void CImagePreviewCtrl::_Init() {

	ATLASSERT(gdiplusToken == NULL);
	Gdiplus::GdiplusStartupInput gdiplusStartupInput;
	Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);


	ATLASSERT (m_semaphore == NULL);
	m_exiting = false;
	TCHAR semaphoreName[128];
	_stprintf_s(semaphoreName, _T("xl::image::preview::semaphore@0x%08x"), (DWORD)this);
	m_semaphore = ::CreateSemaphore(NULL, 0, 5, semaphoreName);
	assert (m_semaphore != NULL);
	m_thread = (HANDLE)_beginthreadex(NULL, 0, _LoadImageThread, this, 0, NULL);

	m_pDoc->subscribe(this);
}

void CImagePreviewCtrl::_DeInit() {
	m_pDoc->unsubscribe(this);

	ATLASSERT (m_semaphore != NULL);

	m_exiting = true;
	ReleaseSemaphore(m_semaphore, 1, NULL);
	::WaitForSingleObject(m_thread, 500);

	CloseHandle(m_semaphore);
	m_semaphore = NULL;

	_RemoveBitmap();

	if (gdiplusToken != NULL) {
		Gdiplus::GdiplusShutdown(gdiplusToken);
		gdiplusToken = NULL;
	}
}

unsigned __stdcall CImagePreviewCtrl::_LoadImageThread(void *param) {
	CImagePreviewCtrl *pThis = (CImagePreviewCtrl *)param;
	assert (pThis != NULL);

	tstring fileName;

	for (;;) {
		::WaitForSingleObject(pThis->m_semaphore, INFINITE);
		if (pThis->m_exiting) {
			break;
		}

		::EnterCriticalSection(&pThis->m_cs);
		if (_tcsicmp(fileName.c_str(), pThis->m_fileNameLoading.c_str()) == 0) {
			::LeaveCriticalSection(&pThis->m_cs);
			continue;
		} else {
			fileName = pThis->m_fileNameLoading;
		}
		::LeaveCriticalSection(&pThis->m_cs);

		// load 
		Gdiplus::Bitmap *bmp = Gdiplus::Bitmap::FromFile(fileName.c_str());
		if (bmp && bmp->GetLastStatus() == Gdiplus::Ok) {
			int w = bmp->GetWidth();
			int h = bmp->GetHeight();

			CRect rc;
			pThis->GetClientRect(rc);

			if (w > rc.Width() || h > rc.Height()) {
				int thumbnail_width, thumbnail_height;
				thumbnail_height = rc.Height();
				thumbnail_width = (int)(w * (float)rc.Height() / (float)h);
				if ((float)w / (float)rc.Width() > (float)h / (float)rc.Height()) {
					thumbnail_width = rc.Width();
					thumbnail_height = (int)(h * (float)rc.Width() / (float)w);
				}
				Gdiplus::Bitmap *thumbnail = 
					(Gdiplus::Bitmap *)bmp->GetThumbnailImage(
					                                          thumbnail_width,
					                                          thumbnail_height,
					                                          NULL, NULL
					                                         );
				if (thumbnail && thumbnail->GetLastStatus() == Gdiplus::Ok) {
					std::swap(thumbnail, bmp);
					delete thumbnail;
					thumbnail = NULL;
					// Now bmp is thumbnail
				} else {
					// report error
					delete bmp;
					bmp = NULL;
				}
			}

		} else {
			// TODO: report the error
		}

		HBITMAP hBitmap = NULL;
		if (bmp && bmp->GetLastStatus() == Gdiplus::Ok) {
			if (bmp->GetHBITMAP(0, &hBitmap) != Gdiplus::Ok) {
				hBitmap = NULL;
				// todo report error
			}
			delete bmp;
			bmp = NULL;
		}


		if (hBitmap) {
			pThis->_AttachBitmap(fileName, hBitmap);
		}
		fileName.clear();
	}

	return 0;
}

void CImagePreviewCtrl::_AttachBitmap(const tstring &fileName, HBITMAP bitmap) {
	::EnterCriticalSection(&m_cs);

	_Cache::iterator it = m_cache.find(fileName);
	ATLASSERT (it == m_cache.end());
	m_cache[fileName] = bitmap;

	if (_tcsicmp(m_fileNameLoading.c_str(), fileName.c_str()) != 0) {
		if (m_isLoadingFileRemoved) {
			m_isLoadingFileRemoved = false;
			m_cache.erase(m_cache.find(fileName)); // it should be removed
			::DeleteObject(bitmap);
		} else {
			// there exists an new image to be loaded, we just wait
		}
	} else {
		m_fileName = fileName;
		m_bitmap = bitmap;
		m_fileNameLoading.clear();
		m_isLoadingFileRemoved = false; // think loading, remove, then add the same, loaded
	}
	::LeaveCriticalSection(&m_cs);

	Invalidate();
}

void CImagePreviewCtrl::_RemoveBitmap(const tstring &fileName) {
	::EnterCriticalSection(&m_cs);
	if (fileName.length() == 0) {
		for (_Cache::iterator it = m_cache.begin(); it != m_cache.end(); ++ it) {
			if (it->second != NULL) {
				::DeleteObject(it->second);
				it->second = NULL;
			}
		}
		m_cache.clear();

		m_fileName = _T("");
		m_bitmap = NULL; // already in cache, and be freed.

		if (m_fileNameLoading.length() > 0) {
			m_fileNameLoading.clear();
			m_isLoadingFileRemoved = true;
		} else {
			if (m_isLoadingFileRemoved) {
				int i = 0;
			}
		}

		Invalidate();

	} else {
		_Cache::iterator it = m_cache.find(fileName);
		if (it == m_cache.end()) {
			// not in cache, just do nothing
		} else {
			HBITMAP hbmp = it->second;
			ATLASSERT (hbmp);
			::DeleteObject(hbmp);
			m_cache.erase(it);

			if (_tcsicmp(m_fileName.c_str(), fileName.c_str()) == 0) {
				m_fileName.clear();
				m_bitmap = NULL;
				Invalidate();
			}
		}

		if (_tcsicmp(m_fileNameLoading.c_str(), fileName.c_str()) == 0) {
			m_fileNameLoading.clear();
			m_isLoadingFileRemoved = true;
		}
	}
	::LeaveCriticalSection(&m_cs);
}

void CImagePreviewCtrl::_DrawText(HDC hdc, TCHAR *text, const CRect &rc) {
	CDCHandle dc(hdc);
	DWORD style = DT_CENTER | DT_VCENTER | DT_SINGLELINE;
	int oldMode = dc.SetBkMode(TRANSPARENT);
	COLORREF oldColor = dc.SetTextColor(RGB(255,255,255));
	HFONT oldFont = dc.SelectFont((HFONT)::GetStockObject(DEFAULT_GUI_FONT));

	CRect rect = rc;
	dc.DrawText(text, -1, rect, style);
	rect.OffsetRect(-1, -1);
	dc.SetTextColor(RGB(0,0,0));
	dc.DrawText(text, -1, rect, style);

	dc.SelectFont(oldFont);
	dc.SetTextColor(oldColor);
	dc.SetBkMode(oldMode);
}

HWND CImagePreviewCtrl::create(HWND hWndParent, 
                               ATL::_U_RECT rect,
                               LPCTSTR szWindowName,
                               DWORD dwStyle, 
                               DWORD dwExStyle,
                               ATL::_U_MENUorID MenuOrID,
                               LPVOID lpCreateParam) {
	ATLASSERT (::IsWindow(hWndParent));
	ATOM atom = GetWndClassInfo().Register(&m_pfnSuperWindowProc);

	dwStyle |= GetWndStyle(0);
	dwExStyle |= GetWndExStyle(0);

	return _Base::Create(hWndParent, rect, szWindowName, dwStyle, dwExStyle, MenuOrID, atom, lpCreateParam);
}

bool CImagePreviewCtrl::setFile(const tstring &fileName) {

	::EnterCriticalSection(&m_cs);
	if (fileName.length() == 0) {
		m_fileName = fileName;
		m_bitmap = NULL;
		if (m_fileNameLoading.length() > 0) {
			m_fileNameLoading.clear();
		}
	} else {
		_Cache::iterator it = m_cache.find(fileName);
		if (it == m_cache.end()) {
			m_fileNameLoading = fileName;
			::ReleaseSemaphore(m_semaphore, 1, NULL);
		} else {
			m_fileName = fileName;
			m_bitmap = it->second;
			m_fileNameLoading.clear();
		}
	}
	::LeaveCriticalSection(&m_cs);

	Invalidate();
	return true;
}


LRESULT CImagePreviewCtrl::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) {
	_Init();
	return TRUE;
}

LRESULT CImagePreviewCtrl::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) {
	_DeInit();
	return 0;
}

LRESULT CImagePreviewCtrl::OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) {

	::EnterCriticalSection(&m_cs);
	PAINTSTRUCT ps;
	HDC hdc = BeginPaint(&ps);
	CDCHandle dc(hdc);

	CRect rc;
	GetClientRect(rc);

	dc.FillSolidRect(rc, ::GetSysColor(COLOR_3DFACE));
	if (m_bitmap != NULL) {
		int x = 0, y = 0, cx = rc.Width(), cy = rc.Height();
		BITMAP bmp;
		if (::GetObject(m_bitmap, sizeof(bmp), &bmp) != 0) {
			x = (rc.Width() - bmp.bmWidth) / 2;
			y = (rc.Height() - bmp.bmHeight) / 2;
			cx = bmp.bmWidth;
			cy = bmp.bmHeight;
		}

		HDC hMemDC = ::CreateCompatibleDC(hdc);
		HGDIOBJ oldObj = ::SelectObject(hMemDC, m_bitmap);
		::BitBlt(hdc, x, y, cx, cy, hMemDC, 0, 0, SRCCOPY);
		::SelectObject(hMemDC, oldObj);
		::DeleteDC(hMemDC);

		if (m_fileNameLoading.length() > 0) {
			// _DrawText(hdc, _T("loading"), rc);
		}

	} else {
		if (m_fileNameLoading.length() > 0) {
			_DrawText(hdc, _T("loading"), rc);
		} else {
			_DrawText(hdc, _T("Preview"), rc);
		}
	}

	EndPaint(&ps);
	::LeaveCriticalSection(&m_cs);

	return 0;
}

LRESULT CImagePreviewCtrl::OnEraseBkGnd(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) {
	return TRUE;
}

LRESULT CImagePreviewCtrl::OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled) {
	return 0;
}

void CImagePreviewCtrl::onEvent(CxDocument::IObserver::EVT evt, void *param) {
	if (evt == CxDocument::EVT_REMOVE) {
		FileInfo *pfi = (FileInfo *)param;
		ATLASSERT (pfi != NULL);
		_RemoveBitmap(pfi->pathname);
	} else if (evt == CxDocument::EVT_CLEAR) {
		_RemoveBitmap();
	} else {
		// we don't care other event
	}
}
